﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using RpcClient.Attr;
using RpcHelper;
using RpcHelper.TaskTools;
using RpcModel;
using RpcModular.Config;
using RpcModular.Domain;
using RpcModular.Model;
using RpcModularModel.Accredit;
using RpcModularModel.Accredit.Model;
using RpcModularModel.Accredit.Msg;

namespace RpcModular.Accredit
{
    /// <summary>
    /// 授权模块
    /// </summary>
    [RpcClient.Attr.ClassLifetimeAttr(ClassLifetimeType.单例)]
    internal class AccreditService: IAccreditController
    {
        private static ConcurrentDictionary<Guid, UserAccreditDomain> _AccreditList = new ConcurrentDictionary<Guid, UserAccreditDomain>();

        /// <summary>
        /// 当前请求的AccreditId
        /// </summary>
        [ThreadStatic]
        private static Guid? _AccreditId;

        public Guid? AccreditId => _AccreditId;

        static AccreditService()
        {
            TaskManage.AddTask(new TaskHelper("刷新授权信息!", new TimeSpan(0, 0, 10), new Action(_Clear)));
            RpcClient.RpcClient.Route.AddRoute<AccreditRefresh>("AccreditRefresh", _AccreditRefresh);
        }
        public AccreditService()
        {

        }

        public virtual void ClearAccredit()
        {
            _AccreditId = null;
        }
        public virtual void SetAccredit(Guid? accreditId)
        {
            _AccreditId = accreditId;
        }
        /// <summary>
        /// 
        /// </summary>
        private static void _Clear()
        {
            if (_AccreditList.IsEmpty)
            {
                return;
            }
            int time = HeartbeatTimeHelper.HeartbeatTime;
            Guid[] keys = _AccreditList.Keys.ToArray();
            if (keys.Length > 0)
            {
                keys.ForEach(a =>
                {
                    if (_AccreditList.TryGetValue(a, out UserAccreditDomain data))
                    {
                        if (!data.CheckStatus())
                        {
                            _AccreditList.TryRemove(a, out data);
                        }
                    }
                });
            }
        }

        private static void _AccreditRefresh(AccreditRefresh obj)
        {
            _AccreditList.TryRemove(obj.AccreditId, out _);
        }

        public void KickOutAccredit(string applyId)
        {
            new KickOutAccredit
            {
                CheckKey = applyId
            }.Send();
        }
        /// <summary>
        /// 申请临时授权
        /// </summary>
        /// <param name="role">角色列表</param>
        /// <param name="isInherit">是否继承</param>
        /// <param name="expire">授权过期时间(秒)</param>
        /// <returns>授权码</returns>
        public Guid ApplyTempAccredit(string[] role, bool isInherit, int? expire = null)
        {
            if (!AccreditId.HasValue)
            {
                throw new ErrorException("rpc.accredit.null");
            }
            CheckAccredit(AccreditId.Value);
            ApplyAccreditRes res = new ApplyAccredit
            {
                ParentId = AccreditId,
                AccreditId = Guid.NewGuid(),
                AccreditRole = role,
                IsInherit = isInherit,
                Expire = expire
            }.Send();
            return _AddAccredit(res.Accredit, null, res.StateVer);
        }
        public Guid ApplyTempAccredit(string applyId, string[] roleId, UserState state, bool isInherit, int? expire = null)
        {
            if (!AccreditId.HasValue)
            {
                throw new ErrorException("rpc.accredit.null");
            }
            CheckAccredit(AccreditId.Value);
            ApplyAccreditRes res = new ApplyAccredit
            {
                ParentId = AccreditId,
                AccreditId = Guid.NewGuid(),
                AccreditRole = roleId,
                State = state.ToJson(),
                ApplyId = applyId,
                IsInherit = isInherit,
                Expire = expire
            }.Send();
            return _AddAccredit(res.Accredit, null, res.StateVer);
        }
        public Guid AddAccredit(string applyId, string[] roleId, UserState state, int? expire = null)
        {
            MsgSource source = RpcClient.RpcClient.CurrentSource;
            roleId = roleId.Add(source.SystemType);
            string json = state == null ? null : Tools.Json(state);
            ApplyAccreditRes res = new ApplyAccredit
            {
                Expire = expire,
                ApplyId = applyId,
                RoleType = ModularConfig.AccreditRoleType,
                AccreditId = Guid.NewGuid(),
                AccreditRole = roleId,
                State = json
            }.Send();
            state.BindAccreditId(res.Accredit.AccreditId, RpcClient.RpcClient.GroupTypeVal, source.RpcMerId);
            return _AddAccredit(res.Accredit, json, res.StateVer);
        }

        private static Guid _AddAccredit(AccreditRes res, string state, long stateVer)
        {
            UserAccreditDomain accredit = new UserAccreditDomain(stateVer, res, state);
            accredit = _AccreditList.GetOrAdd(accredit.AccreditId, accredit);
            return accredit.AccreditId;
        }
        private static UserAccreditDomain _AddAccredit(AccreditDatum datum)
        {
            UserAccreditDomain accredit = new UserAccreditDomain(datum);
            accredit = _AccreditList.GetOrAdd(accredit.AccreditId, accredit);
            return accredit;
        }
        protected static UserAccreditDomain _GetAccredit(Guid accreditId)
        {
            if (!_AccreditList.TryGetValue(accreditId, out UserAccreditDomain accredit))
            {
                AccreditDatum res = new GetAccredit
                {
                    AccreditId = accreditId
                }.Send();
                return _AddAccredit(res);
            }
            else if (accredit.IsError)
            {
                throw new ErrorException(accredit.Error);
            }
            return accredit;
        }

        public void ToUpdate(Guid accreditId, UserState state, string[] roleId, int? expire = null)
        {
            UserAccreditDomain accredit = _GetAccredit(accreditId);
            accredit.ToUpdate(state, roleId, expire);
        }
        public virtual void SetCurrentAccredit(Guid accreditId)
        {
            CheckIsAccredit(accreditId);
            _AccreditId = accreditId;
        }
        public static void Cancel(Guid accreditId)
        {
            UserAccreditDomain accredit = _GetAccredit(accreditId);
            accredit.CancelAccredit();
        }

        internal static void SetUserState(Guid accreditId, UserState state)
        {
            UserAccreditDomain accredit = _GetAccredit(accreditId);
            accredit.SetUserState(state);
        }

        internal static IUserState SetUserState(Guid accreditId, UserState state, Func<IUserState, IUserState, IUserState> upFun)
        {
            UserAccreditDomain accredit = _GetAccredit(accreditId);
            accredit.SetUserState(state, upFun);
            return accredit.GetUserState(state.GetType());
        }

        public void CheckAccredit(Guid accreditId)
        {
            if (!_AccreditList.TryGetValue(accreditId, out UserAccreditDomain accredit))
            {
                AccreditDatum res = new GetAccredit
                {
                    AccreditId = accreditId
                }.Send();
                accredit = _AddAccredit(res);
            }
            if (accredit.IsError)
            {
                throw new ErrorException(accredit.Error);
            }
        }
        public bool CheckIsAccredit(Guid accreditId)
        {
            if (!_AccreditList.TryGetValue(accreditId, out UserAccreditDomain accredit))
            {
                AccreditDatum res = new GetAccredit
                {
                    AccreditId = accreditId
                }.Send();
                accredit = _AddAccredit(res);
            }
            return !accredit.IsError;
        }
    }
}
